14. Grails and Spring

이번 장은 어떻게 Grails를 Spring 프레임워크와 통합하는지 또는 Spring 프레임워크 위에서 구축하는지에 관심이 있는 전문적인 사용자를 위한 것이다. 또한 런타임 Grails 설정을 하는것에 대해 고민하는 플러그인 개발자에게 유용하다.

14.1 The Underpinnings of Grails

Grails는 사실 Spring MVC 어플리케이션이 변장한 것이다. Spring MVC는 Spring Framework에 내재된 MVC 웹 어플리케이션 프레임워크이다. Spring MVC가 사용하기에 쉬운가 라는 점에서 Structs만큼 어렵지만, Grails는 멋지게 설계되고 구조화 되었다. Grails는 다른 프레임워크의 꼭대기에 만들었기에 가장 완벽한 프레임워크가 되었다.

다음과 같은 부분에서 Grails는 Spring MVC를 확대화(leverage) 한다.

달리 말하자면 Grails는 전체에 걸쳐서 Spring을 품고 동작한다.

The Grails ApplicationContext

Spring 개발자들은 종종 Grails ApplicationContext 인스턴스가 어떻게 생성되는지 이해하고 싶어한다. 그 기본구조는 다음과 같다.

Configured Spring Beans(설정된 Spring Bean)

대부분의 Grails 설정은 런타임에 일어난다. 각 플러그인은 아마도 Spring Bean을 설정 할 것이다. Spring Bean은 ApplicationContext에 등록되어 있다. 한 참조가 어떤 Bean으로 설정 되기 위해(to which beans are configured) 레퍼런스 가이드를 참조한다. 그것은 각 Grails 플러그인의 어떤 설명이나 플러그인들이 어떤 Bean을 설정할지에 대한 참조 가이드이다(Most of Grails' configuration happens at runtime. Each plug-in may configure Spring beans that are registered with the ApplicationContext. For a reference as to which beans are configured refer to the reference guide which describes each of the Grails plug-ins and which beans they configure).

14.2 Configuring Additional Beans

Using XML(XML 사용)

Bean은 Grails 어플리케이션의 grails-app/conf/spring/resources.xml 파일을 사용하여 설정 할 수 있다. 이 파일은 일반적인 Spring XML 파일이며 Spring 문서는 Spring Bean을 어떤식으로 설정하는지에 대한 가장 좋은 참고자료이다. 간단한 예제로서 다음과 같은 형식으로 하나의 Bean을 설정할 수 있다:

<bean id="myBean" class="my.company.MyBeanImpl"></bean>

이와 같이 myBean으로 이름지어서 Bean을 한번 설정하면 Grails의 컨트롤러들, 태그라이브러리들, 서비스들 기타 등등에서 사용할 있도록 자동으로 연결된다:

class ExampleController {

def myBean }

Referencing Existing Beans(존재하는 Bean을 참조하기)

resources.xml 파일에 정의된 Bean은 관례에 따라 Grails 클래스를 참조할 수 있다. 예를 들어, 한 Bean에서 BookSerivce와 같은 한 서비스에 대한 참조가 필요하면 클래스 이름으로 표현된 프로퍼티(the property name representation)를 사용한다. 예를 들어, BookService의 경우 bookService가 될 것이다:

<bean id="myBean" class="my.company.MyBeanImpl">
	<property name="bookService" ref="bookService" />	
</bean>

Bean은 그 자체로 퍼블릭 지정자(setter)를 필요로 하며 Grails에서는 다음과 같이 정의한다.

package my.company
class MyBeanImpl {
	BookService bookService
}

자바에서라면 다음과 같다:

package my.company;
class MyBeanImpl {
	private BookService bookService;
	public void setBookService(BookService theBookService) {
		this.bookService = theBookService;
	}
}

많은 Grails 설정이 관례적으로 런타임에 이루어지기 때문에 Bean들을 아무데나 정의하면 안된다. Spring 설정을 통해서 참조할 수 있도록 해야 한다. 예를 들어, Grails DataSource를 참조할 필요가 있다면 다음과 같이 할 수 있다:

<bean id="myBean" class="my.company.MyBeanImpl">
	<property name="bookService" ref="bookService" />	
	<property name="dataSource" ref="dataSource" />
</bean>

Hibernate SessionFactory가 필요하다면 다음과 같이 할 수 있다:

<bean id="myBean" class="my.company.MyBeanImpl">
	<property name="bookService" ref="bookService" />	
	<property name="sessionFactory" ref="sessionFactory" />
</bean>

사용가능한 모든 Bean에 대한 참조를 살펴보려면 참조 가이드에서 플러그인 참조를 살펴보라.

Using the Spring DSL(Spring DSL 사용)

Grails가 제공하는 Spring DSL을 사용하고자 한다면 grails-app/conf/spring/resources.groovy 파일을 만들고 다음과 같은 코드를 가지는 beans로 불리는 메소드를 정의해야 한다.

beans {
	// beans here
}

XML 예제에 대한 동일한 설정은 다음과 같이 쓸 수 있다:

beans {
	myBean(my.company.MyBeanImpl) {
		bookService = ref("bookService")
	}	
}

이런 방식에서 주요 장점은 이제 beans 정의안에서 로직을 섞을 수 있다. 환경(environment)에 기반한 예제는 다음과 같다:

import grails.util.*
beans {
	switch(GrailsUtil.environment) {
		case "production":
			myBean(my.company.MyBeanImpl) {
				bookService = ref("bookService")
			}

break case "development": myBean(my.company.mock.MockImpl) { bookService = ref("bookService") } break } }

14.3 Runtime Spring with the Beans DSL

Spring은 매우 강력하지만 XML 기반 문법은 번거롭고 다양한 수준에서 최근에 Spring 2.0에서 추가된 것들 조차 DRY를 위반한다. Grails의 BeanBuider는 Spring과 그 Core를 사용하여 의존성을 서로 연결하는 간단한 방법을 제공하는 것을 목표로 하고 있다.

더불어 Spring의 (XML을 통해) 설정하는 방식은 반드시 정적으로 해야 하고, 런타임에 수정하고 설정하기 매우 힘들 뿐 아니라, 프로그램으로 XML을 생성하는 것보다 자주 오류를 발생시키고 틀리기 쉽다. Grails BeanBuilder는 런타임에 컴포넌트들을 서로 함께 프로그램적으로 연결하는것을 가능하게 함으로써 모든 것을 바꾼다. 그래서 시스템 프로퍼티나 환경 변수에 따라 로직을 적응할 수 있도록 할 수 있다.

이것은 코드가 자신이 속한 환경에 적응할 수 있도록 하며 불필요한 코드의 중복(테스트, 개발, 실제품환경에 대한 서로다른 Spring 설정을 갖는것)을 줄여준다.

The BeanBuilder class(BeanBuilder 클래스)

Grails는 grails.spring.BeanBuilder 클래스를 제공하며 Bean 정의들을 만들기위해 동적 Groovy를 사용한다. 기본 코드는 다음과 같다:

import org.apache.commons.dbcp.BasicDataSource
import org.codehaus.groovy.grails.orm.hibernate.ConfigurableLocalSessionFactoryBean;
import org.springframework.context.ApplicationContext;

def bb = new grails.spring.BeanBuilder()

bb.beans { dataSource(BasicDataSource) { driverClassName = "org.hsqldb.jdbcDriver" url = "jdbc:hsqldb:mem:grailsDB" username = "sa" password = "" } sessionFactory(ConfigurableLocalSessionFactoryBean) { dataSource = dataSource hibernateProperties = [ "hibernate.hbm2ddl.auto":"create-drop", "hibernate.show_sql":true ] }

}

ApplicationContext appContext = bb.createApplicationContext()

plug-ins나 grails-app/conf/spring/resources.groovy 파일에서 새로운 BeanBuider 인스턴스를 새로 만들 필요는 없다. 그 대신 DSL은 묵시적으로 doWithSpring 안에서나 각 beans block에서 사용가능하다.

위의 예제는 BeanBuider 클래스를 이용하여 어떻게 Hibernate를 적절한 dataSource와 설정을 할 수 있는지를 보여준다.

각 메소드 호출(이번 경우는 dataSource나 sessionFactory의 호출)은 필히 Spring에서 Bean의 이름에 매핑된다. 메소드에 대한 첫 번째 인자는 Bean의 클래스이며 마지막 인자는 코드 블럭이다. 블럭의 본체에서 표준 Groovy 문법을 이용하여 Bean의 프로퍼티를 정할 수 있다.

Bean 참조문제는 Bean의 이름을 이용하여 자동적으로 풀어간다. 이것은 위의 예제에서 sessionFactory Bean이 dataSource 참조를 풀어가는 방식을 통해살펴 볼 수 있다.

Bean 관리에 관계된 일정한 특정 프로퍼티는 아래의 코드에서 볼 수 있듯이 빌더에의해 정해질 수 있다:

sessionFactory(ConfigurableLocalSessionFactoryBean) { bean ->
    bean.autowire = 'byName'       // Autowiring behaviour. The other option is 'byType'. [autowire]
    bean.initMethod = 'init'       // Sets the initialisation method to 'init'. [init-method]
    bean.destroyMethod = 'destroy' // Sets the destruction method to 'destroy'. [destroy-method]
    dataSource = dataSource
    hibernateProperties = [ "hibernate.hbm2ddl.auto":"create-drop",
                            "hibernate.show_sql":true  ]
}

'', '' 안의 문자열은 Spring의 XML 정의 안에서의 Bean 속성(Attribute)과 동일한 이름들이다.

Using Constructor Arguments(생성자 인자 사용)

생성자의 인자는 각 메소드의 파라미터를 통해서 정의할 수 있고 Bean의 클래스와 마지막 클로져 사이에 위치한다:

bb.beans {
   exampleBean(MyExampleBean, "firstArgument", 2) {
       someProperty = [1,2,3]
   }
}

Configuring the BeanDefinition (Using factory methods) (BeanDefinition을 설정(팩토리 메소드 사용하여))

클로져의 첫 번째 인자는 Bean 설정 인스턴스의 한 참조이며 팩토리 메소드를 통해 설정하곤 하고 AbstractBeanDefinition 클래스상에서는 어떤 메소드도 실행할 수 있다:

bb.beans {
   exampleBean(MyExampleBean) { bean ->
       bean.factoryMethod = "getInstance"
       bean.singleton = false
       someProperty = [1,2,3]
   }
}

다른 대안으로서 Bean을 설정하기 위해 정의하는 메소드의 리턴 값을 이용할 수도 있다:

bb.beans {
   def example = exampleBean(MyExampleBean) {
       someProperty = [1,2,3]
   }
   example.factoryMethod = "getInstance"
}

Using Factory beans(Factory Bean을 이용)

Spring은 Factory Bean이라는 개념을 정의한다. 그리고 종종 Bean은 클래스로부터 만들어지지 않고 이런 Factory들로부터 만들어진다. 이런 경우 Bean은 어떠한 클래스도 가지지 않으며 대신 Bean으로 Factory Bean의 이름을 넘겨줘야 한다:

bb.beans {
   myFactory(ExampleFactoryBean) {
       someProperty = [1,2,3]
   }
   myBean(myFactory) {
        name = "blah"
   }
}

이 예제를 살펴보면 클래스 대신 myFactory Bean에 대한 참조를 메소드를 정의하는 Bean으로 넘긴다. 또 다른 것은 Factory Bean에서 호출되도록 Factory 메소드의 이름을 제공하는 것이다. 이것은 Groovy의 네임드 파라미터 문법을 이용하여 한다:

bb.beans {
   myFactory(ExampleFactoryBean) {
       someProperty = [1,2,3]
   }
   myBean(myFactory:"getInstance") {
        name = "blah"
   }
}

myBean을 만들기 위해 ExampleFactoryBean의 getInstance메소드를 호출한다.

Creating Bean References at Runtime(런타임에 Bean 참조를 만들기)

때때로 런타임에나 만들어야 할 Bean의 이름을 알게 될 수도 있다. 이런 경우 동적으로 Bean이 정의하는 메소드를 실행하려면 문자열 interpolation을 사용할 수 있다:

def beanName = "example"
bb.beans {
   "${beanName}Bean"(MyExampleBean) {
       someProperty = [1,2,3]
   }
}

이 경우 앞에서 정의된 beanName 변수를 이용하여 Bean 정의 메소드를 실행할 때 사용한다.

게다가 Bean의 이름을 런타임까지 알 수 없기 때문에 다른 Bean들과 함께 사용하려면 그것들을 참조할 필요가 있다. 이럴 경우 ref 메소드를 사용한다:

def beanName = "example"
bb.beans {
   "${beanName}Bean"(MyExampleBean) {
       someProperty = [1,2,3]
   }
   anotherBean(AnotherBean) {
       example = ref("${beanName}Bean")
   }
}

AnotherBean의 example 프로퍼티는 exampleBean에 대한 런타임 참조를 사용해 정해진다. ref 메소드는 또한 BeanBuider의 생성자에서 제공된 부모 ApplicationContext로부터의 Bean들을 참조하기 위해서도 사용한다:

ApplicationContext parent = ...//
der bb = new BeanBuilder(parent)
bb.beans {
   anotherBean(AnotherBean) {
       example = ref("${beanName}Bean", true)
   }
}

두번째 파라미터 true는 참조가 parent context에서 Bean을 찾도록 지정한다.

Using Anonymous (Inner) Beans(익명(내부) Bean 이용)

Bean의 한 프로퍼티를 Bean 타입으로 인자를 받을 수 있는 코드로 설정해서 익명 내부 Bean을 이용할 수 있다:

bb.beans {
  marge(Person.class) {
      name = "marge"
      husband =  { Person p ->
 	              name = "homer"
		      age = 45
                      props = [overweight:true, height:"1.8m"] }
      children = [bart, lisa]
  }
  bart(Person) {
      name = "Bart"
      age = 11
  }
  lisa(Person) {
      name = "Lisa"
      age = 9
  }
}

위의 예제에서 marge Bean의 husband 프로퍼티를 내부 Bean 참조를 생성하는 코드로 정하였다. 또한 Factory Bean을 가지고 있다면 타입을 생략하고 Factory를 설정하는 대신에 넘겨받은 Bean 정의를 사용할 수 있다:

bb.beans {
  personFactory(PersonFactory.class)
  marge(Person.class) {
      name = "marge"
      husband =  { bean ->
                     bean.factoryBean = "personFactory"
                     bean.factoryMethod = "newInstance"
 	              name = "homer"
		      age = 45
                      props = [overweight:true, height:"1.8m"] }
      children = [bart, lisa]
  }
}

Abstract Beans and Parent Bean Definitions(추상 Bean과 부모 Bean 정의)

추상 Bean 정의를 만들기위해서 아무 클래스도 받지 않는 클래스를 정의한다:

class HolyGrailQuest {
	   def start() { println "lets begin" }
}
class KnightOfTheRoundTable {
   String name
   String leader
   KnightOfTheRoundTable(String n) {
      this.name = n
   }
   HolyGrailQuest quest

def embarkOnQuest() { quest.start() } }

def bb = new grails.spring.BeanBuilder() bb.beans { abstractBean { leader = "Lancelot" } … }

leader 프로퍼티가 “Lancelot”값을 갖도록 하는 추상 Bean을 정의하였다. 이 추상 Bean을 이용하기 위해서 자식 Bean의 부모로 Bean을 정한다:

bb.beans {
  …
  quest(HolyGrailQuest)
  knights(KnightOfTheRoundTable, "Camelot") { bean ->
      bean.parent = abstractBean
      quest = quest
  }
}

부모 Bean을 쓸 때 Bean의 다른 프로퍼티를 정하기전에 parent 프로퍼티를 먼저 정해야한다.

어떤 추상 Bean이 클래스를 가지도록 하려면 다음과 같이 할 수 있다.

def bb = new grails.spring.BeanBuilder()
bb.beans {                                          
  abstractBean(KnightOfTheRoundTable) { bean ->
      bean.'abstract' = true                  
      leader = "Lancelot"
  }
  quest(HolyGrailQuest)
  knights("Camelot") { bean ->
      bean.parent = abstractBean
      quest = quest
  }
}

위의 예제에서 KnightOfTheroundTable이라는 타입의 추상 Bean을 만들고 Bean의 인자를 추상화하는데 사용했다. 그리고 나서 knights Bean을 클래스 없이 정의하고 부모 Bean에서 클래스를 상속한다.

Adding Variables to the Binding (Context) ((Context) 바인딩에 변수를 더하기)

스크립트로부터 Bean을 로드하고 있다면 Groovy 바인딩 객체를 생성함으로써 사용할 바인딩을 설정할 수 있다:

def bb = new BeanBuilder("classpath:*SpringBeans.groovy")
def binding = new Binding()
binding.foo = "bar"

bb.binding = binding

def ctx = bb.createApplicationContext()

Loading Bean Definitions from the File System(파일시스템으로부터 Bean 정의를 불러오기)

외부의 Groovy 스크립트를 불러오기위해 BeanBuilder 클래스를 사용할 수 있다. Groovy 스크립트는 아래와 같이 경로 매칭 문법을 사용하여 Bean을 정의하고 있다:

def bb = new BeanBuilder("classpath:*SpringBeans.groovy")

def applicationContext = bb.createApplicationContext()

BeanBuilder는 SpringBeans.groovy로 끝나는 클래스 경로에서 Groovy 파일들을 불러올 것이며 그 파일들을 Bean 정의로 파싱한다. 그 예제가 아래와 같다:

beans = {
	dataSource(BasicDataSource) {
		driverClassName = "org.hsqldb.jdbcDriver"
		url = "jdbc:hsqldb:mem:grailsDB"
		username = "sa"
		password = ""
	}
        sessionFactory(ConfigurableLocalSessionFactoryBean) {
              dataSource = dataSource
              hibernateProperties = [ "hibernate.hbm2ddl.auto":"create-drop",
                                      "hibernate.show_sql":true  ]
        }

}

14.4 Property Placeholder Configuration

Grails는 프로퍼티 위치지정 설정의 개념을 제공한다. 이것은 Spring의 PropertyPlaceholderConfigurer의 확장 버전을 통해 제공한다. 그리고 외부화된 설정(externalized configuration)과 함께 묶여서 사용될때 유용하다.

자바 프로퍼티 파일의 ConfigSlurper 스크립트에서 정의된 설정들은 grails-app/conf/spring/resources.xml 에서 Spring 설정을 위한 위치지정 값들로 사용된다. 예를 들어 grails-app/conf/Config.groovy 파일(또는 외부화된 설정)에서 다음 목록들entries을 들 수 있다:

database.driver="com.mysql.jdbc.Driver"
database.dbname="mysql:mydb"

그리고 위치지정을 resources.xml파일에서 다음과 같은 ${..} 형식과 비슷하게 지정할 수 있다:

<bean id="dataSource" class="org.springframework.jdbc.datasource.DriverManagerDataSource">
   <property name="driverClassName"><value>${database.driver}</value></property>
   <property name="url"><value>jdbc:${database.dbname}</value></property>
 </bean>

14.5 Property Override Configuration

Grails는 프로퍼티 오버라이드 설정의 개념을 제공한다. 이것은 Spring의 PropertyOverrideConfigurer의 확장 버전을 통해 제공한다. 이것은 외부화된 설정(externalized configuration)과 함께 묶여서 사용될때 일반적으로 유용하다.

기본적으로 Bean에서 설정을 오버라이드 할 수 있는 Bean 코드를 정의하는 ConfigSlurper 스크립트를 제공할 수 있다:

beans {
   bookService.webServiceURL = "http://www.amazon.com"
}

오버라이드는 Spring ApplicationContext가 생성되기 전에 적용된다. 그 형식은 아래와 같다:

[bean name].[property name] = [value]

또한 Bean으로 이름이 시작되는 각 Entry를 써서 자바 프로퍼티 파일을 제공할 수 있다:

beans.bookService.webServiceURL=http://www.amazon.com